<?php

/**
 * Ignus Framework
 *
 * @author Sergey Palyanov <sergey.palyanov@gmail.com>
 * @link http://code.google.com/p/ignus-php/
 * @license http://apache.org/licenses/LICENSE-2.0
 * @package Ignus
 * @subpackage Mock
 * @since 0.1
 */

namespace Ignus\Test\Mock;

use Ignus\Test\Assert;

/**
 * @package Ignus
 * @subpackage Mock
 * @since 0.5
 */
class Invokator
{
    /**
     * List of invoked method
     * @var array
     */
    private $invokations = array();

    /**
     * List of expected method invokations
     * @var array
     * @since 0.6.0
     */
    private $expectations = array();

    /**
     * Log method invokation
     * @param string $methodName Name of invoked method
     * @param array $args Method invokation arguments
     * @return void
     */
    public function invoke($methodName, array $args)
    {
        $key = self::hash($methodName, $args);
        if (!isset($this->invokations[$key]))
            $this->invokations[$key] = 0;

        $this->invokations[$key]++;
    }

    /**
     * Register method invoke expectation
     * @param string $methodName
     * @param array $args
     * @param integer $times
     * @return void
     * @since 0.6.0
     */
    public function expects($methodName, array $args, $times)
    {
        $key = self::hash($methodName, $args);
        $this->expectations[$key] = array(
            'name'  =>  $methodName,
            'args'  =>  $args,
            'times' =>  $times,
        );
    }

    /**
     * Check whether all expectations were met
     * @return void
     * @throws Ignus\Test\AssertionException if
     */
    public function check()
    {
        foreach ($this->expectations as $key => $expectation) {
            if (!isset($this->invokations[$key])) {
                if ($expectation['times'] > 0)
                    Assert::fail(sprintf("Method %s was expected %d time(s) but never invoked",
                        $expectation['name'], $expectation['times']));
                continue;
            }

            if ($this->invokations[$key] > $expectation['times'])
                Assert::fail(sprintf("Method %s was invoked more than %d expected time(s)",
                    $expectation['name'], $expectation['times']));

            if ($this->invokations[$key] < $expectation['times'])
                Assert::fail(sprintf("Method %s was invoked less than %d expected time(s)",
                    $expectation['name'], $expectation['times']));
        }
    }

    /**
     * Turns method name and arguments into string suitable for array key
     * @static
     * @param string $name Method name
     * @param array $args Method invokation arguments
     * @return string Result hash
     * @since 0.6
     */
    public static function hash($name, array $args)
    {
        foreach ($args as &$arg) {
            if (is_array($arg))
                self::sortArray($arg);
        }

        return $name . sha1(json_encode($args));
    }

    /**
     * Sort multidimensional array by keys
     * @static
     * @param array $array
     * @return void
     * @since 0.6
     */
    private static function sortArray(array &$array)
    {
        ksort($array);
        foreach ($array as &$element)
            if (is_array($element))
                self::sortArray($element);
    }
}
